package cn.weichuyu.snippet.gccfb.util;

import cn.weichuyu.snippet.gccfb.annotation.TargetConvertInfo;
import lombok.Data;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;
import java.util.Set;

@Data
public class ConverterCodeGenerator {
    private String targetClassSuffix = "DTO";
    private String sourceClassSuffix = "BO";

    public void beginGenerate(Class clazz) {
        beginGenerate(clazz, "target", "source");
    }

    protected void beginGenerate(Class clazz, String target, String source) {
        int layer = 0;
        String className = acquireTargetClassName(clazz.getSimpleName());
        String initName = acquireTargetClassName(clazz.getSimpleName());
        this.printlnCode("public static " + className + " convert(" + clazz.getSimpleName() + " source){", layer);
        this.printlnCode("if(" + source + "==null){", layer + 1);
        this.printlnCode("return null;", layer + 2);
        this.printlnCode("}", layer + 1);
        this.printlnCode(className + " " + target + " = new " + initName + "();", layer + 1);
        generateCode(clazz, target, source, layer + 1);
        this.printlnCode("return " + target + ";", layer + 1);
        this.printlnCode("}", layer);

    }

    protected void generateCode(Class clazz, String target, String source, int layer) {
        if (layer > 10) {
            return;
        }
        if (clazz == null) {
            return;
        }
        generateCode(clazz.getSuperclass(), target, source, layer);
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //确定类型名
            String originFieldName = field.getName();
            String targetFieldName = field.getName();
            boolean isClassInnerNotStatic = false;
            if (field.isAnnotationPresent(TargetConvertInfo.class)) {
                TargetConvertInfo convertInfo = field.getAnnotation(TargetConvertInfo.class);
                if (convertInfo.targetFieldName() != null && convertInfo.targetFieldName().length() > 0) {
                    targetFieldName = convertInfo.targetFieldName();
                }
                isClassInnerNotStatic = convertInfo.isClassInnerNotStatic();
            } else {
                continue;
            }
            //确定set方法名
            String setMethodName = this.acquireTargetSetMethodName(targetFieldName);
            //确定get方法名
            String getMethodName = this.acquireOriginGetMethodName(originFieldName);
            Class targetFieldType = field.getType();
            //如果是简单类
            if (judgeIfNotNeedComplexHandle(targetFieldType)) {
                /**
                 * dto.setAaa(vo.getAaa());
                 */
                this.printlnSimpleSetGetCode(target, setMethodName, getMethodName, source, layer);
            } else if (Collection.class.isAssignableFrom(targetFieldType)) {
                Type genericType = field.getGenericType();
                if (genericType == null) continue;
                // 如果是泛型参数的类型
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    //得到泛型里的class类型对象
                    Class<?> genericClazz = (Class<?>) pt.getActualTypeArguments()[0];
                    if (judgeIfNotNeedComplexHandle(genericClazz)) {
                        this.printlnSimpleSetGetCode(target, setMethodName, getMethodName, source, layer);
                    } else {
                        String subtargetClassFull = acquireTargetClassName(genericClazz.getName());
                        String subtargetClass = acquireTargetClassName(genericClazz.getSimpleName());
                        String subtarget = "list";

                        String codeStartStr = "List<" + subtargetClassFull + "> " + subtarget + ";";
                        String codeEndStr = "}).collect(Collectors.toList());";
                        if (Set.class.isAssignableFrom(targetFieldType)) {
                            subtarget = "set";
                            codeStartStr = "Set<" + subtargetClassFull + "> " + subtarget + ";";
                            codeEndStr = "}).collect(Collectors.toSet());";
                        }

                        String subsource = source + "." + getMethodName + "()";
                        String lamdatarget = "obj";
                        String lamdasource = "n";
                        this.printlnCode("if(" + subsource + "!= null && !" + subsource + ".isEmpty()) {", layer);
                        //new
                        // this.printlnCode("List<" + subtargetClassFull + "> " + subtarget + ";", layer + 1);
                        this.printlnCode(codeStartStr, layer + 1);
                        //lamda
                        this.printlnCode(subtarget + " = " + subsource + ".stream().map( " + lamdasource + " -> {", layer + 1);
                        {
                            //in lamda
                            if (genericClazz.getName().contains("$") && isClassInnerNotStatic) {
                                //如果是内部动态类
                                this.printlnCode(subtargetClassFull + " " + lamdatarget + " = " + target + ".new " + subtargetClass + "();", layer + 2);
                            } else {
                                //如果是外部类或者内部静态类
                                this.printlnCode(subtargetClassFull + " " + lamdatarget + " = " + "new " + subtargetClassFull + "();", layer + 2);
                            }
                            generateCode(genericClazz, lamdatarget, lamdasource, layer + 2);
                            this.printlnCode("return " + lamdatarget + ";", layer + 2);
                        }
                        this.printlnCode(codeEndStr, layer + 1);
                        // this.printlnCode("}).collect(Collectors.toList());", layer + 1);
                        printlnSimpleSetGetCode(target, setMethodName, subtarget, layer + 1);
                        this.printlnCode("}", layer);
                    }
                }

            } else {
                //如果是其他
                /**
                 * if(source.getResult() != null){
                 *      ResultDTO resultDTO = new ResultDTO();
                 *      target.setResult(resultDTO);
                 * }
                 */
                String subtargetClassFull = acquireTargetClassName(targetFieldType.getName());
                String subtargetClass = acquireTargetClassName(targetFieldType.getSimpleName());
                String subtarget = acquireTargetVarName(subtargetClass, layer);
                String subsource = source + "." + getMethodName + "()";
                this.printlnCode("if(" + subsource + "!= null) {", layer);
                if (targetFieldType.getName().contains("$") && isClassInnerNotStatic) {
                    //如果是内部动态类
                    this.printlnCode(subtargetClassFull + " " + subtarget + " = " + target + ".new " + subtargetClass + "();", layer + 1);
                } else {
                    //如果是外部类或者内部静态类
                    this.printlnCode(subtargetClassFull + " " + subtarget + " = " + "new " + subtargetClassFull + "();", layer + 1);
                }
                generateCode(targetFieldType, subtarget, subsource, layer + 1);
                printlnSimpleSetGetCode(target, setMethodName, subtarget, layer + 1);
                this.printlnCode("}", layer);
            }
        }


    }

    protected boolean judgeIfNotNeedComplexHandle(Class targetFieldType) {
        return targetFieldType.isPrimitive()
                || targetFieldType.equals(String.class)
                || targetFieldType.equals(Date.class)
                || targetFieldType.equals(Boolean.class)
                || Number.class.isAssignableFrom(targetFieldType);
    }

    protected void printlnSimpleSetGetCode(String target, String setMethodName, String getMethodName, String source, int layer) {
        this.printlnCode(target + "." + setMethodName + "(" + source + "." + getMethodName + "());", layer);
    }

    protected void printlnSimpleSetGetCode(String target, String setMethodName, String var, int layer) {
        this.printlnCode(target + "." + setMethodName + "(" + var + ");", layer);
    }

    protected String acquireTargetVarName(String className, int layer) {
        return className.substring(0, 1).toLowerCase() + className.substring(1);
    }

    protected String acquireTargetSetMethodName(String fieldName) {
        return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    protected String acquireOriginGetMethodName(String fieldName) {
        return "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    protected void printlnCode(String code, int layer) {
        for (int i = 0; i < layer; i++) {
            System.out.print("    ");
        }
        System.out.println(code);
    }

    protected String acquireTargetClassName(String className) {
        if (className.contains(".")) {
            String[] classNames = className.split("\\.");
            String realClassName = classNames[classNames.length - 1];
            if (realClassName.contains("$")) {
                String[] realclassNames = realClassName.split("\\$");
                String result = "";
                for (String str : realclassNames) {
                    if (str.endsWith(sourceClassSuffix)) {
                        result = result + str.substring(0, str.length() - sourceClassSuffix.length()) + targetClassSuffix + ".";
                    } else {
                        result = result + str + targetClassSuffix + ".";
                    }
                }
                return result.substring(0, result.length() - 1);
            }
            if (realClassName.endsWith(sourceClassSuffix)) {
                return realClassName.substring(0, realClassName.length() - sourceClassSuffix.length()) + targetClassSuffix;
            }
            return realClassName + targetClassSuffix;
        }
        if (className.endsWith(sourceClassSuffix)) {
            return className.substring(0, className.length() - sourceClassSuffix.length()) + targetClassSuffix;
        }
        return className + targetClassSuffix;
    }

}
